﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Silverlight.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Laharsub.Client;
using System.Threading;
using System.Windows.Browser;

namespace Laharsub.Client.Silverlight.Test
{
    [TestClass]
    public class PubsubClientTest : SilverlightTest
    {
        static TimeSpan defaultTimeout = TimeSpan.FromSeconds(15); // you may want to increase this when debugging
        //static TimeSpan defaultTimeout = TimeSpan.FromHours(2);
        string baseAddress = "http://localhost/ps/memory";

        void AssertException(Type ex, Action action)
        {
            try
            {
                action();
                Assert.Fail("Action did not result in an expected exception.");
            }
            catch (Exception e)
            {
                Assert.AreEqual(e.GetType(), ex);
            }
        }

        void EnsureUriAvailable(Uri uri, string errorMessage)
        {
            Exception e = null;
            bool done = false;

            WebClient client = new WebClient();
            client.DownloadStringCompleted += delegate(object sender, DownloadStringCompletedEventArgs args)
            {
                e = args.Error;
                done = true;
            };
            client.DownloadStringAsync(uri);
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done, errorMessage);
                Assert.IsNull(e, errorMessage);
            });
            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Asynchronous]
        [Description("Check preconditions for running unit tests: clientaccesspolicy.xml must be available at http://localhost/clientaccesspolicy.xml.")]
        public void A1_EnsureClientAccessPolicyConfigured()
        {
            this.EnsureUriAvailable(
                new Uri("http://localhost/clientaccesspolicy.xml"),
                "ClientAccessPolicy.xml file is not accessible at http://localhost/clientaccesspolicy.xml. It is required for the Silverlight unit tests. Please check the clientaccesspolicy.xml file in the Laharsub.Client.Silverlight.Test.Web project for more information.");
        }

        [TestMethod]
        [Asynchronous]
        [Description("Check preconditions for running unit tests: Laharsub service must be running.")]
        public void A2_EnsureLaharsubServerRunning()
        {
            bool done = false;
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    done = true;
                },
                delegate(Exception ex)
                {
                    e = ex; done = true;
                }
            );
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                string error = "Laharsub server is not running. Laharsub server must be accessible at " + this.baseAddress + ". It is required for the Silverlight unit tests. Please run the server from the Visual Studio solution or from the command line using Laharsub.exe /console.";
                Assert.IsTrue(done, error);
                Assert.IsNull(e, error);
            });
            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Description("No base address - should be constructed relative to XAP.")]
        public void B1_ConstructorTest()
        {
            PubsubClient client = new PubsubClient();
            Assert.AreEqual(client.BaseAddress.AbsolutePath, "/ps/memory/");
        }

        [TestMethod]
        [Description("Invalid null as base address")]
        public void B2_ConstructorTest()
        {
            this.AssertException(typeof(ArgumentException), delegate() { new PubsubClient(null); });
        }

        [TestMethod]
        [Description("Invalid relative URI as base address")]
        public void B3_ConstructorTest()
        {
            this.AssertException(typeof(ArgumentException), delegate() { new PubsubClient("foo"); });
        }

        [TestMethod]
        [Description("Valid absolute URI as base address")]
        public void B4_ConstructorTest()
        {
            PubsubClient client = new PubsubClient("uri://foo");
            Assert.AreEqual(client.BaseAddress, new Uri("uri://foo/"));
        }

        void EnqueueConditionalWithTimeout(Func<bool> condition)
        {
            this.EnqueueConditionalWithTimeout(condition, defaultTimeout);
        }

        void EnqueueConditionalWithTimeout(Func<bool> condition, TimeSpan timeout)
        {
            DateTime start = DateTime.Now;
            base.EnqueueConditional(() => condition() || DateTime.Now.Subtract(start) >= timeout);
        }

        [TestMethod]
        [Asynchronous]
        [Description("Create a topic.")]
        public void C_CreateTopic()
        {
            int tid = -1;
            bool done = false;
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done = true;
                },
                delegate(Exception ex)
                {
                    e = ex; done = true;
                }
            );
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done);
                Assert.IsNull(e);
                Assert.IsTrue(tid > 0);
            });
            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Asynchronous]
        [Description("Create a topic and publish to it.")]
        public void D_PublishOne()
        {
            int tid = -1;
            bool done = false;
            Exception e = null;
            PubsubMessage message = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done = true;
                },
                delegate(Exception ex)
                {
                    e = ex; done = true;
                }
            );
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done);
                Assert.IsNull(e);
                Assert.IsTrue(tid > 0);
                done = false;
                client.PublishAsync(
                    tid,
                    "Hello D_PublishOne",
                    delegate(PubsubMessage m)
                    {
                        message = m;
                        done = true;
                    },
                    delegate(PubsubMessage m, Exception ex)
                    {
                        e = ex;
                        message = m;
                        done = true;
                    }
                );
            });
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done, "The test timed out without receiving a response to publish message");
                Assert.IsNull(e);
                Assert.IsNotNull(message);
                Assert.AreEqual(message.TopicId, tid);
                Assert.AreEqual(message.GetBodyAsString(), "Hello D_PublishOne");
                Assert.IsTrue(message.MessageId > 0);
                Assert.AreEqual(message.ContentType, "text/plain");
            });

            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Asynchronous]
        [Description("Subscribe to a topic, publish a message, verify it is delivered.")]
        public void E1_SubscribePublishOne()
        {
            int tid = -1;
            bool done = false;
            Exception e = null;
            PubsubMessage message = null;
            Subscription sub = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done = true;
                },
                delegate(Exception ex)
                {
                    e = ex; done = true;
                }
            );
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done);
                Assert.IsNull(e);
                Assert.IsTrue(tid > 0);
                done = false;
                client.SubscribeAsync(new Subscription
                {
                    TopicId = tid,
                    From = 1,
                    OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                    {
                        message = m;
                        sub = s;
                        done = true;
                    },
                    OnError = delegate(Subscription s, Exception ex)
                    {
                        sub = s;
                        e = ex;
                        done = true;
                    }
                });
                client.PublishAsync(tid, "Hello E1_SubscribePublishOne");
            });
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done, "The test timed out without receiving a message");
                Assert.IsNotNull(sub);
                Assert.IsNull(e);
                Assert.IsNotNull(message);
                Assert.AreEqual(message.TopicId, tid);
                Assert.AreEqual(message.GetBodyAsString(), "Hello E1_SubscribePublishOne");
                Assert.IsTrue(message.MessageId > 0);
                Assert.AreEqual(message.ContentType, "text/plain");
                Assert.AreEqual(sub.TopicId, tid);
                Assert.IsTrue(sub.From > message.MessageId);
            });

            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Asynchronous]
        [Description("Subscribe to one topic, publish three messages, verify all are received.")]
        public void E2_SubscribePublishThree()
        {
            int sum = 0;
            int tid = -1;
            bool done = false;
            Exception e = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid = topicId; done = true;
                },
                delegate(Exception ex)
                {
                    e = ex; done = true;
                }
            );
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done);
                Assert.IsNull(e);
                Assert.IsTrue(tid > 0);
                done = false;
                client.SubscribeAsync(new Subscription
                {
                    TopicId = tid,
                    From = 1,
                    OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                    {
                        sum += int.Parse(m.GetBodyAsString());
                        done = sum == 7;
                    },
                    OnError = delegate(Subscription s, Exception ex)
                    {
                        e = ex;
                        done = true;
                    }
                });
                client.PublishAsync(tid, "1");
                client.PublishAsync(tid, "2");
                client.PublishAsync(tid, "4");
            });
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done, "The test timed out without receiving a message");
                Assert.IsNull(e);
                Assert.AreEqual(sum, 7, "Not all published messages were received");
            });

            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Asynchronous]
        [Description("Subscribe to two topics, publish to one of them, verify the message is delivered to the right subscription.")]
        public void E3_SubscribeTwoPublishOne()
        {
            int tid1 = -1;
            int tid2 = -1;
            bool done = false;
            Exception e1 = null;
            Exception e2 = null;
            PubsubMessage message1 = null;
            PubsubMessage message2 = null;
            Subscription sub1 = null;
            Subscription sub2 = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid1 = topicId; 
                },
                delegate(Exception ex)
                {
                    e1 = ex; 
                }
            );
            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid2 = topicId;
                },
                delegate(Exception ex)
                {
                    e2 = ex;
                }
            );
            this.EnqueueConditionalWithTimeout(() => (tid1 > 0 || e1 != null) && (tid2 > 0 || e2 != null));
            this.EnqueueCallback(delegate()
            {
                Assert.IsNull(e1);
                Assert.IsTrue(tid1 > 0);
                Assert.IsNull(e2);
                Assert.IsTrue(tid2 > 0);

                client.SubscribeAsync(new Subscription
                {
                    TopicId = tid1,
                    From = 1,
                    OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                    {
                        message1 = m;
                        sub1 = s;
                        done = true;
                    },
                    OnError = delegate(Subscription s, Exception ex)
                    {
                        sub1 = s;
                        e1 = ex;
                        done = true;
                    }
                });
                client.SubscribeAsync(new Subscription
                {
                    TopicId = tid2,
                    From = 1,
                    OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                    {
                        message2 = m;
                        sub2 = s;
                        done = true;
                    },
                    OnError = delegate(Subscription s, Exception ex)
                    {
                        sub2 = s;
                        e2 = ex;
                        done = true;
                    }
                });
                client.PublishAsync(tid1, "Hello E3_SubscribeTwoPublishOne");
            });
            this.EnqueueConditionalWithTimeout(() => done);
            this.EnqueueCallback(delegate()
            {
                Assert.IsTrue(done, "The test timed out without receiving a message");
                Assert.IsNotNull(sub1);
                Assert.IsNull(e1);
                Assert.IsNotNull(message1);
                Assert.AreEqual(message1.TopicId, tid1);
                Assert.AreEqual(message1.GetBodyAsString(), "Hello E3_SubscribeTwoPublishOne");
                Assert.IsTrue(message1.MessageId > 0);
                Assert.AreEqual(message1.ContentType, "text/plain");
                Assert.AreEqual(sub1.TopicId, tid1);
                Assert.IsTrue(sub1.From > message1.MessageId);
                Assert.IsNull(sub2);
                Assert.IsNull(e2);
                Assert.IsNull(message2);
            });

            this.EnqueueTestComplete();
        }

        [TestMethod]
        [Description("Subscribe and cancel subscription, then publish - the message should never be received.")]
        [Asynchronous]
        public void F1_SubscribeCancel()
        {
            int tid1 = -1;
            Exception e1 = null;
            PubsubMessage message1 = null;
            Subscription sub1 = null;
            PubsubClient client = new PubsubClient(this.baseAddress);

            client.CreateTopicAsync(
                delegate(int topicId)
                {
                    tid1 = topicId;
                },
                delegate(Exception ex)
                {
                    e1 = ex;
                }
            );
            this.EnqueueConditionalWithTimeout(() => tid1 > 0 || e1 != null);
            this.EnqueueCallback(delegate()
            {
                Assert.IsNull(e1);
                Assert.IsTrue(tid1 > 0);

                client.SubscribeAsync(new Subscription
                {
                    TopicId = tid1,
                    From = 1,
                    OnMessageReceived = delegate(Subscription s, PubsubMessage m)
                    {
                        message1 = m;
                        sub1 = s;
                    },
                    OnError = delegate(Subscription s, Exception ex)
                    {
                        sub1 = s;
                        e1 = ex;
                    }
                });
                client.Unsubscribe(tid1);
                client.PublishAsync(tid1, "Hello F1_SubscribeCancel");
            });

            this.EnqueueConditionalWithTimeout(() => sub1 != null, TimeSpan.FromSeconds(3));
            this.EnqueueCallback(delegate()
            {
                Assert.IsNull(e1);
                Assert.IsNull(message1);
                Assert.IsNull(sub1);
            });

            this.EnqueueTestComplete();
        }
    }
}